use self::conf::project_conf::AppPageCaptureItemValue;
use super::prequire::adb::collect::DeviceSpecifications;
use chrono::prelude::*;
use std::io;
use std::path::{Path, PathBuf};
use std::{
  collections::HashMap,
  fs::{self, canonicalize},
};

mod action;
mod catcher;
pub mod conf;
mod device;
mod parser;
mod scene;
mod trainer;

pub use action::ProjectActionCommands;
pub use catcher::ProjectScreenCommands;
pub use catcher::{CatchOptions, CaptureCategory, OldCatcher as Catcher};
pub use conf::ProjectConfigCommands;
pub use conf::{
  device_conf::DeviceConf,
  project_conf::{AppPageSection, AppPageTapSection, DevicePoint, DeviceShape, ProjectConf},
};
use conf::{
  read_device_conf_from_toml, read_project_conf_from_toml, write_device_conf_to_toml,
  write_project_conf_to_toml,
};
pub use device::ProjectDeviceCommands;
pub use parser::ParseOptions;
pub use scene::CaptureScenes;
pub use trainer::ProjectTrainCommands;

#[derive(Debug)]
pub struct Project {
  location: PathBuf,
  proj_conf: ProjectConf,
  device_conf: Option<DeviceConf>,
}

impl Project {
  pub fn new(name: String, app_name: String, description: Option<String>) -> Self {
    let name_str = name.as_str();
    let relative = format!("./{}", name_str);
    let path = Path::new(relative.as_str());
    if let Ok(_) = fs::create_dir_all(path) {
    } else {
      eprintln!("can't mkdir<{}>", path.display());
    }

    let project_conf_location = PathBuf::from(canonicalize(relative).unwrap());

    let proj_conf = ProjectConf::new(name, app_name, description);
    write_project_conf_to_toml(&project_conf_location, &proj_conf, false)
      .expect("write proj conf failed");
    Project {
      location: project_conf_location,
      proj_conf,
      device_conf: None,
    }
  }
  pub fn from_toml<P>(from_location: P) -> io::Result<Self>
  where
    P: AsRef<Path>,
  {
    // let proj_conf = ProjectConf::read_from_toml(from_location)?;
    let proj_conf = read_project_conf_from_toml(&from_location)?;
    let mut device_conf = None;
    if let Some(sn) = &proj_conf.app.device {
      device_conf = read_device_conf_from_toml(&from_location, sn).ok();
    }
    Ok(Project {
      location: PathBuf::from(from_location.as_ref()),
      proj_conf,
      device_conf,
    })
  }
  pub fn calc_capture_dir<P1>(&self, base_dir: P1, train: bool) -> String
  where
    P1: AsRef<Path>,
  {
    let fragment = iif!(train, CaptureCategory::Train, CaptureCategory::Screenshot);
    let capture_dir = format!(
      "{}/{}/{}/{:?}_{:?}",
      self.location.as_os_str().to_str().unwrap(),
      &self.proj_conf.app.capture_dir.display(),
      base_dir.as_ref().display(),
      fragment,
      Utc::now().timestamp_millis(),
    );

    return capture_dir;
  }
  fn sync_to_proj_app(&mut self, device_sn: String) {
    self.proj_conf.app.device = Some(device_sn);
    write_project_conf_to_toml(&self.location, &self.proj_conf, true)
    .expect("sync app device sn to proj conf failed");
  } 
  fn apply_to_device(&mut self, specs: &mut Vec<Option<DeviceSpecifications>>) {
    let mut sn = String::from("");
    if let Some(index) = specs.iter().position(|value| {
      value.as_ref().map_or(false, |value| match value {
        DeviceSpecifications::SerialNum(v) => {
          sn = v.to_string();
          true
        }
        _ => false,
      })
    }) {
      specs.swap_remove(index);
    }
    println!("sn:{:?}", sn);
    let mut device_conf = DeviceConf::new(sn);
    for spec in specs.iter().filter_map(|f| f.as_ref()) {
      match spec {
        DeviceSpecifications::ScreenDensity(d) => {
          device_conf.screen_density = *d;
        }
        DeviceSpecifications::ScreenSize(w, h) => {
          device_conf.screen_size = [*w, *h];
        }
        DeviceSpecifications::Keyboard(k) => {
          device_conf.keyboard = k.to_string();
        }
        _ => {}
      }
    }
    println!("device_conf=>{:?}", device_conf);
    write_device_conf_to_toml(&self.location, &device_conf, true)
      .expect("write device conf failed");
    self.sync_to_proj_app(device_conf.sn.to_string());
    self.device_conf = Some(device_conf);
  }
  fn apply_to_page(
    &mut self,
    name: String,
    taps: &Vec<(String, DevicePoint)>,
    capture: &Vec<(String, AppPageCaptureItemValue)>,
  ) {
    println!("config to page `{}` capture=>{:?}", name, capture);
    println!("hermes.toml=>`{:?}`", self);

    let taps = taps
      .iter()
      .map(|v| AppPageTapSection::new(v.0.to_string(), v.1.clone()))
      .collect();
    let capture_items: HashMap<String, AppPageCaptureItemValue> =
      HashMap::from_iter(capture.into_iter().map(|p| p.to_owned()));

    let page = AppPageSection::new(name, taps, capture_items);
    self.proj_conf.app.set_page(page);
    write_project_conf_to_toml(&self.location, &self.proj_conf, true)
      .expect("write proj conf failed");
  }
  fn resolve_train_base_dir(&self) -> PathBuf {
    let train_dir = PathBuf::from(&self.proj_conf.app.train_dir);
    let train_dir = iif!(train_dir.starts_with("/"), train_dir, {
      let mut p = PathBuf::from(&self.location);
      p.push(&self.proj_conf.app.train_dir);
      p
    });
    train_dir
  }
  pub fn serve_action_commands(&self, command: &ProjectActionCommands) {
    action::serve_commands(self, command);
  }
  pub fn serve_screen_commands(&self, command: &ProjectScreenCommands) {
    catcher::serve_commands(self, command);
  }
  pub fn serve_train_commands(&self, command: &ProjectTrainCommands) {
    trainer::serve_commands(self, command);
  }
  pub fn serve_device_commands(&mut self, command: &ProjectDeviceCommands) {
    device::serve_commands(self, command);
  }
  pub fn serve_config_commands(&mut self, command: &ProjectConfigCommands) {
    conf::serve_commands(self, command);
  }
}
